﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Threading;

namespace MTO.Framework.Web.Security
{
    /// <summary>
    /// Gère le stockage des informations d'appartenance (membership) pour une application ASP.NET dans une ou plusieurs base de données.
    /// Ce provider interrogera en même temps tous les autres MembershipProvider qui seront inscrit dans le fichier Web.Config. De cette façon,
    /// il est possible d'avoir certains utilisateurs qui sont accessible avec ActiveDirectoryMembershipProvider ainsi que d'autres utilisateurs accessible
    /// avec SqlMembershipProvider sans avoir à gérer manuellement tous les environnements de stockage d'information. Par example, Membership.ValidateUser
    /// interrogera un par un les MembershipProviders inscrient jusqu'à temps qu'il trouve une réponse.
    /// </summary>
    public class MultiplexMembershipProvider : MembershipProvider
    {
        private MembershipProviderCollection membershipProviders;
        private string appName;

        public MultiplexMembershipProvider()
        {
        }

        public override string ApplicationName
        {
            get
            {
                return this.appName;
            }

            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentNullException("ApplicationName", "Doit être renseigné");
                }

                if (value.Length > 100)
                {
                    throw new ArgumentException("ApplicationName", "Trop long");
                }

                this.appName = value;
            }
        }

        public override bool EnablePasswordReset
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override bool EnablePasswordRetrieval
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override int MinRequiredPasswordLength
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override int PasswordAttemptWindow
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override string PasswordStrengthRegularExpression
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public override bool RequiresUniqueEmail
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        public MembershipProviderCollection MembershipProviders
        {
            get
            {
                if (this.membershipProviders == null)
                {
                    MembershipProviderCollection allMembershipProviders = null;

                    Interlocked.Exchange<MembershipProviderCollection>(ref allMembershipProviders, Membership.Providers);

                    this.membershipProviders = new MembershipProviderCollection();

                    foreach (MembershipProvider membershipProvider in allMembershipProviders)
                    {
                        if (membershipProvider.Name != this.Name)
                        {
                            this.membershipProviders.Add(membershipProvider);
                        }
                    }

                    this.membershipProviders.SetReadOnly();
                }

                return this.membershipProviders;
            }

            set
            {
                this.membershipProviders = value;
            }
        }

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            if (string.IsNullOrEmpty(name))
            {
                name = "MultiplexMembershipProvider";
            }

            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Multiplex membership provider");
            }

            base.Initialize(name, config);
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            return this.GetUser(username).ChangePassword(oldPassword, newPassword);
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            return this.GetUser(username).ChangePasswordQuestionAndAnswer(password, newPasswordQuestion, newPasswordAnswer);
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out System.Web.Security.MembershipCreateStatus status)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            MembershipUser user = this.GetUser(username);

            if (user == null)
            {
                return false;
            }

            return this.MembershipProviders[user.ProviderName].DeleteUser(username, deleteAllRelatedData);
        }
        
        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            MembershipUserCollection membershipUsers = null;

            foreach (MembershipProvider membershipProviders in this.MembershipProviders)
            {
                int partialTotalRecords = 0;
                MembershipUserCollection partialMembershipUsers = membershipProviders.FindUsersByEmail(emailToMatch, 0, int.MaxValue, out partialTotalRecords);

                if (membershipUsers == null)
                {
                    membershipUsers = partialMembershipUsers;
                }
                else
                {
                    foreach (MembershipUser membershipUser in partialMembershipUsers)
                    {
                        membershipUsers.Add(membershipUser);
                    }
                }

                totalRecords += partialTotalRecords;
            }

            return membershipUsers;
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            MembershipUserCollection membershipUsers = null;

            foreach (MembershipProvider membershipProviders in this.MembershipProviders)
            {
                int partialTotalRecords = 0;
                MembershipUserCollection partialMembershipUsers = membershipProviders.FindUsersByName(usernameToMatch, 0, int.MaxValue, out partialTotalRecords);

                if (membershipUsers == null)
                {
                    membershipUsers = partialMembershipUsers;
                }
                else
                {
                    foreach (MembershipUser membershipUser in partialMembershipUsers)
                    {
                        membershipUsers.Add(membershipUser);
                    }
                }

                totalRecords += partialTotalRecords;
            }

            return membershipUsers;
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;
            MembershipUserCollection membershipUsers = null;

            foreach (MembershipProvider membershipProviders in this.MembershipProviders)
            {
                int partialTotalRecords = 0;
                MembershipUserCollection partialMembershipUsers = membershipProviders.GetAllUsers(0, int.MaxValue, out partialTotalRecords);

                if (membershipUsers == null)
                {
                    membershipUsers = partialMembershipUsers;
                }
                else
                {
                    foreach (MembershipUser membershipUser in partialMembershipUsers)
                    {
                        membershipUsers.Add(membershipUser);
                    }
                }

                totalRecords += partialTotalRecords;
            }

            return membershipUsers;
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            return this.GetUser(username).GetPassword(answer);
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            foreach (MembershipProvider membershipProvider in this.MembershipProviders)
            {
                var user = membershipProvider.GetUser(username, userIsOnline);

                if (user != null)
                {
                    return user;
                }
            }

            return null;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            foreach (MembershipProvider membershipProvider in this.MembershipProviders)
            {
                var user = membershipProvider.GetUser(providerUserKey, userIsOnline);

                if (user != null)
                {
                    return user;
                }
            }

            return null;
        }

        public override string GetUserNameByEmail(string email)
        {
            foreach (MembershipProvider membershipProvider in this.MembershipProviders)
            {
                var user = membershipProvider.GetUserNameByEmail(email);

                if (user != null)
                {
                    return user;
                }
            }

            return null;
        }

        public override string ResetPassword(string username, string answer)
        {
            return this.GetUser(username).ResetPassword(answer);
        }

        public override bool UnlockUser(string userName)
        {
            return this.GetUser(userName).UnlockUser();
        }

        public override void UpdateUser(MembershipUser user)
        {
            this.MembershipProviders[user.ProviderName].UpdateUser(user);
        }

        public override bool ValidateUser(string username, string password)
        {
            foreach (MembershipProvider membershipProvider in this.MembershipProviders)
            {
                bool valid = membershipProvider.ValidateUser(username, password);

                if (valid)
                {
                    return true;
                }
            }

            return false;
        }

        private MembershipUser GetUser(string username)
        {
            return this.GetUser(username);
        }
    }
}
